﻿Imports System.Reflection
Imports System.Text

Namespace Util

    ''' <summary>
    ''' リフレクションを使用した各種操作を提供するユーティリティクラス。
    ''' </summary>
    ''' <history>[sonodar] 2011/07/06 グラビアからコピーして新規作成</history>
    Public NotInheritable Class ReflectionUtil

#Region "自動型変換用の静的Privateメンバ"

        '// 値変換検証用のDelegate
        Private Delegate Function TryParseCallback(Of T)(ByVal s As String, ByRef value As T) As Boolean

        '// 値型変換用のDelegate
        Private Delegate Function ParseCallback(Of T)(ByVal s As String) As T

        '// 値変換検証DelegateのMap
        Private Shared ReadOnly _tryParseDelegateMap As IDictionary(Of Type, [Delegate])

        '// 値型変換DelegateのMap
        Private Shared ReadOnly _parseDelegateMap As IDictionary(Of Type, [Delegate])

        ''' <summary>
        ''' 値変換検証DelegateのMapを生成する。
        ''' </summary>
        Private Shared Function CreateTryParseMap() As IDictionary(Of Type, [Delegate])
            Dim map As IDictionary(Of Type, [Delegate]) = New Dictionary(Of Type, [Delegate])
            Dim dlgt0 As TryParseCallback(Of Boolean) = (AddressOf Boolean.TryParse)
            Dim dlgt1 As TryParseCallback(Of Short) = (AddressOf Short.TryParse)
            Dim dlgt2 As TryParseCallback(Of Integer) = (AddressOf Integer.TryParse)
            Dim dlgt3 As TryParseCallback(Of Long) = (AddressOf Long.TryParse)
            Dim dlgt4 As TryParseCallback(Of Single) = (AddressOf Single.TryParse)
            Dim dlgt5 As TryParseCallback(Of Double) = (AddressOf Double.TryParse)
            Dim dlgt6 As TryParseCallback(Of DateTime) = (AddressOf DateTime.TryParse)
            Dim dlgt7 As TryParseCallback(Of Byte) = (AddressOf Byte.TryParse)
            Dim dlgt8 As TryParseCallback(Of Decimal) = (AddressOf Decimal.TryParse)
            Dim dlgt9 As TryParseCallback(Of Char) = (AddressOf Char.TryParse)
            map.Add(GetType(Boolean), dlgt0)
            map.Add(GetType(Boolean?), dlgt0)
            map.Add(GetType(Short), dlgt1)
            map.Add(GetType(Short?), dlgt1)
            map.Add(GetType(Integer), dlgt2)
            map.Add(GetType(Integer?), dlgt2)
            map.Add(GetType(Long), dlgt3)
            map.Add(GetType(Long?), dlgt3)
            map.Add(GetType(Single), dlgt4)
            map.Add(GetType(Single?), dlgt4)
            map.Add(GetType(Double), dlgt5)
            map.Add(GetType(Double?), dlgt5)
            map.Add(GetType(DateTime), dlgt6)
            map.Add(GetType(DateTime?), dlgt6)
            map.Add(GetType(Byte), dlgt7)
            map.Add(GetType(Byte?), dlgt7)
            map.Add(GetType(Decimal), dlgt8)
            map.Add(GetType(Decimal?), dlgt8)
            map.Add(GetType(Char), dlgt9)
            map.Add(GetType(Char?), dlgt9)
            Return map
        End Function

        ''' <summary>
        ''' 値型変換用DelegateのMapを生成する。
        ''' </summary>
        Private Shared Function CreateParseMap() As IDictionary(Of Type, [Delegate])
            Dim map As IDictionary(Of Type, [Delegate]) = New Dictionary(Of Type, [Delegate])
            Dim dlgt0 As ParseCallback(Of Boolean) = (AddressOf Boolean.Parse)
            Dim dlgt1 As ParseCallback(Of Short) = (AddressOf Short.Parse)
            Dim dlgt2 As ParseCallback(Of Integer) = (AddressOf Integer.Parse)
            Dim dlgt3 As ParseCallback(Of Long) = (AddressOf Long.Parse)
            Dim dlgt4 As ParseCallback(Of Single) = (AddressOf Single.Parse)
            Dim dlgt5 As ParseCallback(Of Double) = (AddressOf Double.Parse)
            Dim dlgt6 As ParseCallback(Of DateTime) = (AddressOf DateTime.Parse)
            Dim dlgt7 As ParseCallback(Of Byte) = (AddressOf Byte.Parse)
            Dim dlgt8 As ParseCallback(Of Decimal) = (AddressOf Decimal.Parse)
            Dim dlgt9 As ParseCallback(Of Char) = (AddressOf Char.Parse)
            map.Add(GetType(Boolean), dlgt0)
            map.Add(GetType(Boolean?), dlgt0)
            map.Add(GetType(Short), dlgt1)
            map.Add(GetType(Short?), dlgt1)
            map.Add(GetType(Integer), dlgt2)
            map.Add(GetType(Integer?), dlgt2)
            map.Add(GetType(Long), dlgt3)
            map.Add(GetType(Long?), dlgt3)
            map.Add(GetType(Single), dlgt4)
            map.Add(GetType(Single?), dlgt4)
            map.Add(GetType(Double), dlgt5)
            map.Add(GetType(Double?), dlgt5)
            map.Add(GetType(DateTime), dlgt6)
            map.Add(GetType(DateTime?), dlgt6)
            map.Add(GetType(Byte), dlgt7)
            map.Add(GetType(Byte?), dlgt7)
            map.Add(GetType(Decimal), dlgt8)
            map.Add(GetType(Decimal?), dlgt8)
            map.Add(GetType(Char), dlgt9)
            map.Add(GetType(Char?), dlgt9)
            Return map
        End Function

        ''' <summary>
        ''' 値型変換DelegateのMapを生成する。
        ''' </summary>
        <System.Diagnostics.DebuggerStepThrough()> _
        Shared Sub New()
            _tryParseDelegateMap = CreateTryParseMap()
            _parseDelegateMap = CreateParseMap()
        End Sub

#End Region

        ''' <summary>
        ''' デフォルトコンストラクタを不可視とすることで、<br/>
        ''' インスタンスの生成を禁止する。
        ''' </summary>
        Private Sub New()
        End Sub

        ''' <summary>
        ''' オブジェクト間で同名のプロパティまたはフィールドをコピーする。<br/>
        ''' Publicで宣言されているフィールドのみコピー可能。
        ''' </summary>
        ''' <param name="srcObj">コピー元のインスタンス</param>
        ''' <param name="destObj">コピー先のインスタンス</param>
        ''' <param name="targets">
        ''' コピーする(excludeがTrueの場合はコピーしない)プロパティ名またはフィールド名
        ''' </param>
        ''' <param name="exclude">
        ''' targetsで指定したフィールドを無視するかどうかのフラグ。<br/>
        ''' 省略した場合はコピーする(False)。
        ''' </param>
        ''' <param name="isAutoCast">
        ''' コピー元とコピー先で型が違った場合に型変換を試みるかどうか。<br/>
        ''' Trueを指定した場合、変換を試みて、変換が成功した場合は<br/>
        ''' 変換したオブジェクトを設定する。<br/>
        ''' 省略した場合は試みない(False)。
        ''' </param>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Sub CopyProperties(Of T)(ByVal srcObj As Object, ByRef destObj As T, _
                                              Optional ByVal targets As IEnumerable(Of String) = Nothing, _
                                              Optional ByVal exclude As Boolean = False, _
                                              Optional ByVal isAutoCast As Boolean = False)
            Try

                '// チェック対象名のDictionary
                Dim isCheck As Boolean = Not CollectionUtil.IsEmpty(targets)
                Dim targetSet As IDictionary(Of String, String) = New Dictionary(Of String, String)()
                If (isCheck) Then
                    For Each target As String In targets
                        targetSet(target) = target
                    Next
                End If

                '// Getプロパティの数だけ値を取得して連想配列に追加
                For Each prop As PropertyInfo In srcObj.GetType.GetProperties()
                    If (Not prop.CanRead) Then
                        Continue For    '// WriteOnlyプロパティは飛ばす
                    End If
                    '// コピー対象外だったら飛ばす
                    If (isCheck AndAlso targetSet.ContainsKey(prop.Name) = exclude) Then
                        Continue For
                    End If
                    '// コピー先オブジェクトに値を設定
                    SetProperty(destObj, prop.Name, _
                                prop.GetValue(srcObj, Nothing), isAutoCast:=isAutoCast)
                Next

                '// フィールドの数だけ値を取得して連想配列に追加
                For Each field As FieldInfo In srcObj.GetType.GetFields()
                    '// コピー対象外だったら飛ばす
                    If (isCheck AndAlso targetSet.ContainsKey(field.Name) = exclude) Then
                        Continue For
                    End If
                    '// コピー先オブジェクトに値を設定
                    SetProperty(destObj, field.Name, _
                                field.GetValue(srcObj), isAutoCast:=isAutoCast)
                Next

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Sub

        ''' <summary>
        ''' Publicなプロパティまたはフィールドから値を取得する。
        ''' </summary>
        ''' <param name="obj">値を取得するオブジェクト</param>
        ''' <param name="name">値を取得するプロパティ名またはフィールド名</param>
        ''' <param name="isError">
        ''' 指定したメンバが存在しなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はしない(False)。<br/>
        ''' Falseを指定して値が見つからなかった場合はNothingを返す。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字、小文字を区別するかどうか。<br/>
        ''' Trueを指定した場合、大文字・小文字を区別しない。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <param name="isNonPublic">
        ''' Publicフィールド(プロパティ)以外を対象とするかどうか。<br/>
        ''' 省略した場合はPublicのみ(False)。
        ''' </param>
        ''' <returns>取得した値</returns>
        Public Shared Function GetProperty(ByVal obj As Object, ByVal name As String, _
                                           Optional ByVal isError As Boolean = False, _
                                           Optional ByVal ignoreCase As Boolean = False, _
                                           Optional ByVal isNonPublic As Boolean = False) As Object
            '// Dictionaryの場合
            If (TypeOf obj Is IDictionary) Then
                Dim Dic As IDictionary = DirectCast(obj, IDictionary)
                If (Dic.Contains(name)) Then
                    Return Dic.Item(name)
                End If
                Return Nothing
            End If

            '// 取得するメンバの属性フラグ
            Dim flag As BindingFlags = BindingFlags.Instance Or BindingFlags.Public

            '// 大文字／小文字の区別なしの場合
            If (ignoreCase) Then
                flag = flag Or BindingFlags.IgnoreCase
            End If

            '// Public以外も取得する場合
            If (isNonPublic) Then
                flag = flag Or BindingFlags.NonPublic
            End If

            Try

                '// 先にプロパティから検索
                Dim prop As PropertyInfo = obj.GetType.GetProperty(name, flag)
                '// 読み取り可能なプロパティであれば値を返す
                If (prop IsNot Nothing AndAlso prop.CanRead) Then
                    Return prop.GetValue(obj, Nothing)
                End If

                '// プロパティから見つからなかった場合はフィールドから検索
                Dim field As FieldInfo = obj.GetType.GetField(name, flag)
                If (field IsNot Nothing) Then
                    '// フィールドの値を返す
                    Return field.GetValue(obj)
                End If

                '// 見つからなかった場合
                If (isError) Then
                    Throw New System.Exception(name + "という読み取り可能なメンバが見つかりません。" + _
                                               obj.GetType.FullName)
                Else
                    Return Nothing
                End If

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Function

        ''' <summary>
        ''' Publicなプロパティまたはフィールドに値を設定する。
        ''' </summary>
        ''' <param name="obj">値を設定するオブジェクト</param>
        ''' <param name="name">値を設定するプロパティ名またはフィールド名</param>
        ''' <param name="value">設定する値</param>
        ''' <param name="isError">
        ''' 指定したメンバが存在しなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はしない(False)。<br/>
        ''' Falseを指定して値が見つからなかった場合は何もしない。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字、小文字を区別するかどうか。<br/>
        ''' Trueを指定した場合、大文字・小文字を区別しない。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <param name="isAutoCast">
        ''' 設定する値と設定先のフィールド(プロパティ)の型が違った場合に型変換を試みるかどうか。<br/>
        ''' Trueを指定した場合、変換を試みて成功した場合は変換したオブジェクトを設定する。<br/>
        ''' 省略した場合は変換を試みない(False)。
        ''' </param>
        ''' <param name="isNonPublic">
        ''' Publicフィールド(プロパティ)以外を対象とするかどうか。<br/>
        ''' 省略した場合はPublicのみ(False)。
        ''' </param>
        Public Shared Sub SetProperty(Of T)(ByRef obj As T, ByVal name As String, ByVal value As Object, _
                                           Optional ByVal isError As Boolean = False, _
                                           Optional ByVal ignoreCase As Boolean = False, _
                                           Optional ByVal isAutoCast As Boolean = False, _
                                           Optional ByVal isNonPublic As Boolean = False)
            '// Dictionaryの場合
            If (TypeOf obj Is IDictionary) Then
                DirectCast(obj, IDictionary)(name) = value
                Return
            End If

            '// 取得するメンバの属性フラグ
            Dim flag As BindingFlags = BindingFlags.Instance Or BindingFlags.Public

            '// 大文字／小文字の区別なしの場合
            If (ignoreCase) Then
                flag = flag Or BindingFlags.IgnoreCase
            End If

            '// Public以外も設定する場合
            If (isNonPublic) Then
                flag = flag Or BindingFlags.NonPublic
            End If

            Try

                '// 先にプロパティから検索
                Dim prop As PropertyInfo = obj.GetType.GetProperty(name, flag)
                '// 書き込み可能なプロパティであれば値を設定
                If (prop IsNot Nothing AndAlso prop.CanWrite) Then
                    If (isAutoCast) Then
                        '// 自動型変換して設定
                        prop.SetValue(obj, AutoCast(value, prop.PropertyType), Nothing)
                    Else
                        prop.SetValue(obj, value, Nothing)
                    End If
                    Return
                End If

                '// プロパティから見つからなかった場合はフィールドから検索
                Dim field As FieldInfo = obj.GetType.GetField(name, flag)
                If (field IsNot Nothing) Then
                    If (isAutoCast) Then
                        '// 自動型変換して設定
                        field.SetValue(obj, AutoCast(value, field.FieldType))
                    Else
                        field.SetValue(obj, value)
                    End If
                    Return
                End If

                '// 見つからなかった場合
                If (isError) Then
                    Throw New System.Exception(name + "という書き込み可能なメンバが見つかりません。" + _
                                               obj.GetType.FullName)
                Else
                    Return
                End If

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Sub

        ''' <summary>
        ''' srcオブジェクトをdestTypeで指定した型に変換可能な場合は変換して返す。<br/>
        ''' 変換不可能な場合はそのまま返す。<br/>
        ''' ユーザ定義型についてはサポートしない。
        ''' </summary>
        ''' <param name="src">変換するオブジェクト</param>
        ''' <param name="destType">変換する型</param>
        ''' <returns>変換後の型</returns>
        Public Shared Function AutoCast(ByVal src As Object, ByVal destType As Type) As Object

            '// Nothingの場合は変換しない
            If (src Is Nothing) Then Return src

            '// DBNullの場合はNULLを返す
            If (TypeOf src Is DBNull) Then Return Nothing

            '// 変換するオブジェクトの型を取得
            Dim srcType As Type = src.GetType

            '// 変換しなくても代入できる型の場合はそのまま返す
            If (srcType.IsAssignableFrom(destType)) Then Return src

            '// 文字列から他の型へ変換する場合
            If (TypeOf src Is String) Then

                Dim s As String = DirectCast(src, String)

                '// 空文字の場合はNothingを返す
                If (CommonUtil.IsEmpty(s)) Then Return Nothing

                '// 変換後の型が自動変換サポート対象の型だった場合
                If (_tryParseDelegateMap.ContainsKey(destType)) Then

                    '// TryParseメソッドを取得
                    Dim parser As [Delegate] = _tryParseDelegateMap(destType)

                    '// TryParseメソッドを実行し、変換不能な場合はNothingを返す
                    If (Not DirectCast(parser.DynamicInvoke(s, Nothing), Boolean)) Then
                        Return Nothing
                    End If

                    '// Parseメソッドを実行して戻り値を返す
                    Return _parseDelegateMap(destType).DynamicInvoke(s)

                End If

            ElseIf (destType.Equals(GetType(String))) Then
                '// String型への変換はToStringを実行するだけ
                Return src.ToString

            ElseIf (srcType.IsPrimitive OrElse TypeOf src Is Decimal) Then

                '// BooleanとDateTimeは特殊なので無視
                If (TypeOf src Is Boolean OrElse TypeOf src Is DateTime) Then
                    Return src
                End If

                '// Short型の値をリフレクションを使用してInteger?などに設定しようとすると
                '// エラーになってしまう。

                '// プリミティブ型だった場合は変換

                If (destType.Equals(GetType(Integer)) OrElse destType.Equals(GetType(Integer?))) Then
                    Return Convert.ToInt32(src)
                ElseIf (destType.Equals(GetType(Long)) OrElse destType.Equals(GetType(Long?))) Then
                    Return Convert.ToInt64(src)
                ElseIf (destType.Equals(GetType(Decimal)) OrElse destType.Equals(GetType(Decimal?))) Then
                    Return Convert.ToDecimal(src)
                ElseIf (destType.Equals(GetType(Short)) OrElse destType.Equals(GetType(Short?))) Then
                    Return Convert.ToInt16(src)
                ElseIf (destType.Equals(GetType(Single)) OrElse destType.Equals(GetType(Single?))) Then
                    Return Convert.ToSingle(src)
                ElseIf (destType.Equals(GetType(Double)) OrElse destType.Equals(GetType(Double?))) Then
                    Return Convert.ToDouble(src)
                ElseIf (destType.Equals(GetType(Byte)) OrElse destType.Equals(GetType(Byte?))) Then
                    Return Convert.ToByte(src)
                ElseIf (destType.Equals(GetType(SByte)) OrElse destType.Equals(GetType(SByte?))) Then
                    Return Convert.ToSByte(src)
                ElseIf (destType.Equals(GetType(UInteger)) OrElse destType.Equals(GetType(UInteger?))) Then
                    Return Convert.ToUInt32(src)
                ElseIf (destType.Equals(GetType(ULong)) OrElse destType.Equals(GetType(ULong?))) Then
                    Return Convert.ToUInt64(src)
                ElseIf (destType.Equals(GetType(UShort)) OrElse destType.Equals(GetType(UShort?))) Then
                    Return Convert.ToUInt16(src)
                End If

            End If

            '// 上記以外の場合はそのまま返す
            Return src

        End Function

        ''' <summary>
        ''' Publicなプロパティ、フィールド、メソッド、イベントなどのメンバ情報を取得する。
        ''' </summary>
        ''' <typeparam name="T">メンバ情報の型(MemberInfo継承クラス:FieldInfo,MethodInfo,PropertyInfoなど)</typeparam>
        ''' <param name="obj">メンバ情報を取得するオブジェクト</param>
        ''' <param name="name">メンバ名</param>
        ''' <param name="isError">
        ''' 指定したメンバが存在しなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はしない(False)。<br/>
        ''' Falseを指定して値が見つからなかった場合は空のリストを返す。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字、小文字を区別するかどうか。<br/>
        ''' Trueを指定した場合、大文字・小文字を区別しない。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <returns>
        ''' メンバ名に該当するメンバ情報(MemberInfo継承クラス)。
        ''' 複数該当した場合はリストで返す。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Function GetMemberInfoList(Of T As MemberInfo)( _
                                                    ByVal obj As Object, _
                                                    ByVal name As String, _
                                                    Optional ByVal isError As Boolean = False, _
                                                    Optional ByVal ignoreCase As Boolean = False) As IList(Of T)
            Dim flag As BindingFlags = (BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.FlattenHierarchy)
            If (ignoreCase) Then
                flag = flag Or BindingFlags.IgnoreCase
            End If

            Dim members As IList(Of T) = New List(Of T)
            Dim targetType As Type = GetType(T)

            For Each member As MemberInfo In obj.GetType.GetMember(name, flag)
                If (targetType.IsAssignableFrom(member.GetType)) Then
                    members.Add(DirectCast(member, T))
                End If
            Next

            Return members

        End Function

        ''' <summary>
        ''' Publicなプロパティ、フィールド、メソッド、イベントなどのメンバ情報を取得する。
        ''' </summary>
        ''' <typeparam name="T">メンバ情報の型(MemberInfo継承クラス:FieldInfo,MethodInfo,PropertyInfoなど)</typeparam>
        ''' <param name="obj">メンバ情報を取得するオブジェクト</param>
        ''' <param name="name">メンバ名</param>
        ''' <param name="isError">
        ''' 指定したメンバが存在しなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はしない(False)。<br/>
        ''' Falseを指定して値が見つからなかった場合はNothingを返す。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字、小文字を区別するかどうか。<br/>
        ''' Trueを指定した場合、大文字・小文字を区別しない。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <returns>
        ''' メンバ名に該当するメンバ情報(MemberInfo継承クラス)。
        ''' 複数該当した場合は先頭の1件。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Function GetMemberInfo(Of T As MemberInfo)( _
                                                    ByVal obj As Object, _
                                                    ByVal name As String, _
                                                    Optional ByVal isError As Boolean = False, _
                                                    Optional ByVal ignoreCase As Boolean = False) As T
            Dim list As IList(Of T) = GetMemberInfoList(Of T)(obj, name, isError, ignoreCase)
            If (CollectionUtil.IsEmpty(list)) Then
                Return Nothing
            End If
            Return list.Item(0)
        End Function

        ''' <summary>
        ''' メソッド名を指定してメソッドを実行する。
        ''' </summary>
        ''' <param name="obj">メソッドを持つオブジェクト</param>
        ''' <param name="name">メソッド名</param>
        ''' <param name="isMissingError">
        ''' メソッドが見つからなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はエラーとしない(False)。
        ''' </param>
        ''' <param name="parameterTypes">メソッド引数の型配列</param>
        ''' <param name="args">メソッドの引数</param>
        ''' <returns>メソッド実行結果</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Function InvokeMethod(Of T)(ByRef obj As T, _
                                            ByVal name As String, _
                                            ByVal isMissingError As Boolean, _
                                            ByVal parameterTypes() As Type, _
                                            ByVal ParamArray args() As Object) As Object
            Try

                '// メソッドを取得
                Dim methodInfo As MethodInfo = obj.GetType.GetMethod(name, parameterTypes)

                '// メソッドを実行して戻り値を返す
                If (methodInfo IsNot Nothing) Then
                    Return methodInfo.Invoke(obj, args)
                End If

                If (isMissingError) Then
                    Throw New System.Exception(name + "という実行可能なメソッドが見つかりません。" + _
                                               obj.GetType.FullName)
                Else
                    Return Nothing
                End If

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Function

        ''' <summary>
        ''' オブジェクトのフィールド(プロパティ)の名称/値を、辞書型に変換して返す。<br/>
        ''' 変換されるのはPublicのメンバで読み取り可能なフィールド(プロパティ)のみ。
        ''' </summary>
        ''' <param name="obj">オブジェクトのインスタンス</param>
        ''' <param name="fields">
        ''' 辞書に格納するフィールド名。<br/>
        ''' 省略した場合はすべてのフィールド。
        ''' </param>
        ''' <param name="exclude">
        ''' Trueを指定した場合、fieldsで指定したフィールド名を格納するのではなく<br/>
        ''' 無視する。省略した場合はFalse。
        ''' </param>
        ''' <returns>フィールド(プロパティ)の名称/値を持ったDictionary</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Function Describe(ByVal obj As Object, _
                                        Optional ByVal fields() As String = Nothing, _
                                        Optional ByVal exclude As Boolean = False) _
                                        As IDictionary(Of String, Object)
            If (TypeOf obj Is IDictionary(Of String, Object)) Then
                Return DirectCast(obj, IDictionary(Of String, Object))
            End If

            '// 戻り値を初期化
            Dim propertyMap As IDictionary(Of String, Object) = New Dictionary(Of String, Object)

            Dim fieldSet As IDictionary(Of String, String) = New Dictionary(Of String, String)

            '// fieldsが指定されているかどうかを保持
            Dim existFields As Boolean = (Not CollectionUtil.IsEmpty(fields))

            If (existFields) Then
                For Each field As String In fields
                    fieldSet(field) = field
                Next
            End If

            Try

                '// クラスのメンバーを取得して繰り返す
                For Each member As MemberInfo In obj.GetType.GetMembers( _
                                BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.FlattenHierarchy)

                    If (TypeOf member Is PropertyInfo) Then

                        '// プロパティの場合
                        Dim propInfo As PropertyInfo = DirectCast(member, PropertyInfo)

                        '// 読み取り可能でなければ飛ばす
                        If (Not propInfo.CanRead) Then
                            Continue For
                        End If

                        '// 検証対象外のフィールドだった場合
                        If (existFields AndAlso fieldSet.ContainsKey(propInfo.Name) = exclude) Then
                            Continue For
                        End If

                        '// プロパティの値を設定
                        propertyMap.Add(propInfo.Name, propInfo.GetValue(obj, Nothing))

                    ElseIf (TypeOf member Is FieldInfo) Then

                        '// フィールドの場合
                        Dim field As FieldInfo = DirectCast(member, FieldInfo)

                        '// 検証対象外のフィールドだった場合
                        If (existFields AndAlso fieldSet.ContainsKey(field.Name) = exclude) Then
                            Continue For
                        End If

                        '// フィールド値を設定
                        propertyMap.Add(field.Name, field.GetValue(obj))
                    End If

                Next

                Return propertyMap

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Function

        ''' <summary>
        ''' サブクラスのすべてのプロパティまたはフィールドの名称と値を文字列に変換する。<br/>
        ''' デバッグ用として使う。ログ出力する場合は必ずIsDebugEnabledなどで囲むこと。<br/>
        ''' インデックスプロパティには対応していない。<br/>
        ''' 指定されたオブジェクトがNULL参照の場合は空文字を出力する。
        ''' </summary>
        ''' <param name="obj">文字列表現を取得するオブジェクトのインスタンス</param>
        ''' <returns>インスタンスに設定されている名称と値の文字列表現</returns>
        Public Shared Shadows Function ToString(ByVal obj As Object) As String

            '// nullの場合は空文字を返す
            If (obj Is Nothing) Then Return String.Empty

            '// プリミティブ型はそのまま返す
            If (obj.GetType.IsPrimitive) Then Return obj.ToString

            '// Stringの場合はそのまま返す
            If (TypeOf obj Is String) Then Return DirectCast(obj, String)

            Dim str As StringBuilder = New StringBuilder

            '// 生成する文字列のプレフィクスは "クラス名:{"
            str.Append(obj.GetType.FullName)                '// クラス名
            str.Append(":"c)
            str.Append("{"c)

            Try

                '// Publicフィールドをすべて取得
                Dim fields() As System.Reflection.FieldInfo = obj.GetType.GetFields()
                If (fields.Length > 0) Then
                    '// 先頭要素にはカンマを付けないで文字列結合
                    str.Append(fields(0).Name)
                    str.Append("="c)
                    str.Append(fields(0).GetValue(obj))
                    '// 2番目以降の要素をカンマ付きで文字列結合
                    For i As Integer = 1 To fields.Length - 1
                        str.Append(","c)
                        str.Append(" "c)
                        str.Append(fields(i).Name)
                        str.Append("="c)
                        str.Append(fields(i).GetValue(obj))
                    Next
                End If

                '// PublicなGetプロパティをすべて取得
                Dim properties() As System.Reflection.PropertyInfo = obj.GetType.GetProperties()
                If (properties.Length > 0) Then
                    'すでに文字列があったら先頭にカンマを追加
                    If (fields.Length > 0) Then
                        str.Append(","c)
                        str.Append(" "c)
                    End If

                    Dim idx As Integer = 0
                    For i As Integer = 0 To properties.Length - 1
                        idx += 1
                        '// 読み取り可能なパラメータなしのプロパティのみ追加
                        If (properties(0).CanRead AndAlso properties(0).GetGetMethod.GetParameters.Length = 0) Then
                            str.Append(properties(0).Name)
                            str.Append("="c)
                            str.Append(properties(0).GetValue(obj, Nothing))
                            Exit For
                        End If
                    Next

                    For i As Integer = idx To properties.Length - 1
                        '// 読み取り可能なパラメータなしのプロパティのみ追加
                        If (properties(i).CanRead AndAlso properties(i).GetGetMethod.GetParameters.Length = 0) Then
                            str.Append(","c)
                            str.Append(" "c)
                            str.Append(properties(i).Name)
                            str.Append("="c)
                            str.Append(properties(i).GetValue(obj, Nothing))
                        End If
                    Next
                End If

                str.Append("}"c)

                '// 生成した文字列を返す
                Return str.ToString

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Function

        ''' <summary>
        ''' フィールド、またはインデックスなしのプロパティから値を取得する。
        ''' </summary>
        ''' <param name="obj">フィールド、またはプロパティを持つオブジェクトのインスタンス</param>
        ''' <param name="member">フィールド、またはプロパティ</param>
        ''' <returns>フィールド、またはプロパティの値</returns>
        Friend Shared Function GetValue(Of T)(ByRef obj As T, ByVal member As MemberInfo) As Object
            Try

                If (TypeOf member Is FieldInfo) Then
                    Return DirectCast(member, FieldInfo).GetValue(obj)
                ElseIf (TypeOf member Is PropertyInfo) Then
                    Return DirectCast(member, PropertyInfo).GetValue(obj, Nothing)
                Else
                    Throw New System.Exception(MethodBase.GetCurrentMethod.DeclaringType.FullName + _
                                            "#GetValue: 対応していない型です。" + member.GetType.FullName)
                End If

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Function

        ''' <summary>
        ''' 指定したメンバが存在するかどうかを返す。
        ''' </summary>
        ''' <param name="obj">メンバを検索するオブジェクト</param>
        ''' <param name="name">メンバ名</param>
        ''' <param name="ignoreCase">
        ''' 大文字、小文字を区別するかどうか。<br/>
        ''' Trueを指定した場合、大文字・小文字を区別しない。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <returns>メンバが存在したらTrue、しなければFalse。</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Function ExistsMember(ByVal obj As Object, _
                                            ByVal name As String, _
                                            Optional ByVal ignoreCase As Boolean = False) As Boolean
            Dim flag As BindingFlags = BindingFlags.Instance Or BindingFlags.Public

            If (ignoreCase) Then
                flag = flag Or BindingFlags.IgnoreCase
            End If

            '// メンバを検索
            Dim memberInfo() As MemberInfo = obj.GetType.GetMember(name, flag)

            Return (Not CollectionUtil.IsEmpty(memberInfo))

        End Function

        ''' <summary>
        ''' オブジェクト配列からカスタム属性配列に変換するためのConverter。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の総称型</typeparam>
        ''' <param name="obj">変換するオブジェクト</param>
        ''' <returns>カスタム属性オブジェクト</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Private Shared Function ConvertAttribute(Of T As System.Attribute)(ByVal obj As Object) As T
            If (obj Is Nothing) Then
                Return Nothing
            End If
            Return DirectCast(obj, T)
        End Function

        ''' <summary>
        ''' Typeからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は複数取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">Typeオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>設定されたカスタム属性の配列</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttributes(Of T As System.Attribute) _
            (ByVal obj As Type, ByVal Inherit As Boolean) As T()
            Dim attrs() As Object = obj.GetCustomAttributes(GetType(T), Inherit)
            Dim converter As Converter(Of Object, T) = (AddressOf ConvertAttribute)
            Return Array.ConvertAll(attrs, converter)
        End Function

        ''' <summary>
        ''' Assemblyからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は複数取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">Assemblyオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>設定されたカスタム属性の配列</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttributes(Of T As System.Attribute) _
            (ByVal obj As Assembly, ByVal Inherit As Boolean) As T()
            Dim attrs() As Object = obj.GetCustomAttributes(GetType(T), Inherit)
            Dim converter As Converter(Of Object, T) = (AddressOf ConvertAttribute)
            Return Array.ConvertAll(attrs, converter)
        End Function

        ''' <summary>
        ''' MemberInfoからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は複数取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">MemberInfoオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>設定されたカスタム属性の配列</returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttributes(Of T As System.Attribute) _
            (ByVal obj As MemberInfo, ByVal Inherit As Boolean) As T()
            Dim attrs() As Object = obj.GetCustomAttributes(GetType(T), Inherit)
            Dim converter As Converter(Of Object, T) = (AddressOf ConvertAttribute)
            Return Array.ConvertAll(attrs, converter)
        End Function

        ''' <summary>
        ''' Typeからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は先頭を取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">Typeオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>
        ''' 設定されたカスタム属性。<br/>
        ''' 取得できなかった場合はNothing。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttribute(Of T As System.Attribute) _
            (ByVal obj As Type, ByVal Inherit As Boolean) As T
            Dim attrs() As T = GetAttributes(Of T)(obj, Inherit)
            If (CollectionUtil.IsEmpty(attrs)) Then
                Return Nothing
            End If
            Return attrs(0)
        End Function

        ''' <summary>
        ''' Assemblyからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は先頭を取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">Assemblyオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>
        ''' 設定されたカスタム属性。<br/>
        ''' 取得できなかった場合はNothing。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttribute(Of T As System.Attribute) _
            (ByVal obj As Assembly, ByVal Inherit As Boolean) As T
            Dim attrs() As T = GetAttributes(Of T)(obj, Inherit)
            If (CollectionUtil.IsEmpty(attrs)) Then
                Return Nothing
            End If
            Return attrs(0)
        End Function

        ''' <summary>
        ''' MemberInfoからカスタム属性を取得する。<br/>
        ''' 複数存在した場合は先頭を取得する。
        ''' </summary>
        ''' <typeparam name="T">カスタム属性の型</typeparam>
        ''' <param name="obj">MemberInfoオブジェクト</param>
        ''' <param name="Inherit">カスタム属性のInherited</param>
        ''' <returns>
        ''' 設定されたカスタム属性。<br/>
        ''' 取得できなかった場合はNothing。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetAttribute(Of T As System.Attribute) _
            (ByVal obj As MemberInfo, ByVal Inherit As Boolean) As T
            Dim attrs() As T = GetAttributes(Of T)(obj, Inherit)
            If (CollectionUtil.IsEmpty(attrs)) Then
                Return Nothing
            End If
            Return attrs(0)
        End Function

        ''' <summary>
        ''' 値を代入可能なフィールド(プロパティ)に対して値を設定する。
        ''' </summary>
        ''' <param name="obj">値を設定するオブジェクト</param>
        ''' <param name="valueType">設定する型</param>
        ''' <param name="value">設定する値</param>
        ''' <param name="isError">
        ''' 設定対象フィールド(プロパティ)がなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はエラーとしない(False)。
        ''' </param>
        ''' <remarks>
        ''' Publicでないフィールド(プロパティ)も対象となる。<br/>
        ''' 同じ型が複数あった場合はすべてのフィールド(プロパティ)に対して設定される。
        ''' </remarks>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Shared Sub SetPropertyByType(Of T)(ByRef obj As T, ByVal valueType As Type, ByVal value As Object, _
                                           Optional ByVal isError As Boolean = False)
            Dim exists As Boolean = False
            Try
                '// 全プロパティを取得
                For Each prop As PropertyInfo In obj.GetType().GetProperties( _
                        BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
                    '// 書き込み可能、かつ、代入可能クラスであったら代入
                    If (prop.CanWrite AndAlso prop.PropertyType.IsAssignableFrom(valueType)) Then
                        prop.SetValue(obj, value, Nothing)
                        exists = True
                    End If
                Next

                '// 全フィールドを取得
                For Each field As FieldInfo In obj.GetType().GetFields( _
                        BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
                    '// 代入可能クラスであったら代入
                    If (field.FieldType.IsAssignableFrom(valueType)) Then
                        field.SetValue(obj, value)
                        exists = True
                    End If
                Next

                If (isError AndAlso Not exists) Then
                    Throw New System.Exception(valueType.FullName + "を代入可能なメンバが見つかりません。" + _
                                               obj.GetType.FullName)
                End If

            Catch e As TargetInvocationException

                '// リフレクションによる内部例外をスローする
                If (e.InnerException IsNot Nothing) Then
                    Throw e.InnerException
                End If

                Throw e

            End Try
        End Sub

        ''' <summary>
        ''' クラス名を指定して、指定したアセンブリの参照アセンブリからTypeを取得する。
        ''' </summary>
        ''' <param name="asmbly">アセンブリ</param>
        ''' <param name="typeName">クラス名</param>
        ''' <param name="isError">
        ''' 見つからなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はエラーとしない(False)。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字・小文字を区別するかどうか。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <returns>
        ''' クラスのTypeオブジェクト。<br/>
        ''' isErrorがFalseの場合に、見つからなかったらNothing。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetTypeFromReference(ByVal asmbly As Assembly, _
                                                    ByVal typeName As String, _
                                                    Optional ByVal isError As Boolean = False, _
                                                    Optional ByVal ignoreCase As Boolean = False) As Type
            Dim findType As Type        '// 検索されたTypeオブジェクト

            '// 参照アセンブリの数だけ繰り返す
            For Each asmblyName As AssemblyName In Assembly.GetEntryAssembly.GetReferencedAssemblies
                '// 参照アセンブリから型を検索
                findType = Assembly.Load(asmblyName).GetType(typeName, False, ignoreCase)

                '// 見つかった場合は返す
                If (findType IsNot Nothing) Then
                    Return findType
                End If

            Next

            If (isError) Then
                Throw New System.Exception(typeName + "というクラスは見つかりません。")
            End If

            Return Nothing

        End Function

        ''' <summary>
        ''' クラス名を指定して、EXE実行アセンブリの参照アセンブリからTypeを取得する。
        ''' </summary>
        ''' <param name="typeName">クラス名</param>
        ''' <param name="isError">
        ''' 見つからなかった場合にエラーとするかどうか。<br/>
        ''' 省略した場合はエラーとしない(False)。
        ''' </param>
        ''' <param name="ignoreCase">
        ''' 大文字・小文字を区別するかどうか。<br/>
        ''' 省略した場合は区別する(False)。
        ''' </param>
        ''' <returns>
        ''' クラスのTypeオブジェクト。<br/>
        ''' isErrorがFalseの場合に、見つからなかったらNothing。
        ''' </returns>
        <System.Diagnostics.DebuggerStepThrough()> _
        Public Overloads Shared Function GetTypeFromReference(ByVal typeName As String, _
                                                    Optional ByVal isError As Boolean = False, _
                                                    Optional ByVal ignoreCase As Boolean = False) As Type
            Return GetTypeFromReference(Assembly.GetEntryAssembly, typeName, isError, ignoreCase)
        End Function

    End Class

End Namespace